/**
  *
  ******************************************************************************
  * @作  者  Musk Han@XTARK
  * @内  容  OLED显示驱动程序
  *
  ******************************************************************************
  */

#include "ax_oled.h"
#include "ax_oled_ascii.h"

// OLED信号定义
#define OLED_DC_L()    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)  // DC
#define OLED_DC_H()    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)

#define OLED_RST_L()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET)  // RES  
#define OLED_RST_H()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET)

// 函数定义
static uint8_t SPI1_Send_Byte(uint8_t dat);
static void OLED_WriteCmd(uint8_t data);
static void OLED_WriteData(uint8_t data);
static void OLED_Set_Pos(uint8_t x, uint8_t y);

/**
 * @简  述  OLED初始化
 * @参  数  无
 * @返回值  无
 */
void AX_OLED_Init(void)
{


	//__HAL_SPI_ENABLE(&hspi1);

	// 启动传输
	SPI1_Send_Byte(0xff);

	OLED_RST_H();
	HAL_Delay(100);
	OLED_RST_L();
	HAL_Delay(200);
	OLED_RST_H();

	OLED_WriteCmd(0xAE); //--turn off oled panel
	OLED_WriteCmd(0x00); //---set low column address
	OLED_WriteCmd(0x10); //---set high column address
	OLED_WriteCmd(0x40); //--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	OLED_WriteCmd(0x81); //--set contrast control register
	OLED_WriteCmd(0xEF); // Set SEG Output Current Brightness
	OLED_WriteCmd(0xA1); //--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	OLED_WriteCmd(0xC8); // Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	OLED_WriteCmd(0xA6); //--set normal display
	OLED_WriteCmd(0xA8); //--set multiplex ratio(1 to 64)
	OLED_WriteCmd(0x3f); //--1/64 duty
	OLED_WriteCmd(0xD3); //-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	OLED_WriteCmd(0x00); //-not offset
	OLED_WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
	OLED_WriteCmd(0x80); //--set divide ratio, Set Clock as 100 Frames/Sec
	OLED_WriteCmd(0xD9); //--set pre-charge period
	OLED_WriteCmd(0xF1); // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	OLED_WriteCmd(0xDA); //--set com pins hardware configuration
	OLED_WriteCmd(0x12);
	OLED_WriteCmd(0xDB); //--set vcomh
	OLED_WriteCmd(0x40); // Set VCOM Deselect Level
	OLED_WriteCmd(0x20); //-Set Page Addressing Mode (0x00/0x01/0x02)
	OLED_WriteCmd(0x02); //
	OLED_WriteCmd(0x8D); //--set Charge Pump enable/disable
	OLED_WriteCmd(0x14); //--set(0x10) disable
	OLED_WriteCmd(0xA4); // Disable Entire Display On (0xa4/0xa5)
	OLED_WriteCmd(0xA6); // Disable Inverse Display On (0xa6/a7)
	OLED_WriteCmd(0xAF); //--turn on oled panel

	AX_OLED_ClearScreen();
}

/**
 * @简  述  OLED屏幕清除
 * @参  数  无
 * @返回值	 无
 */
void AX_OLED_ClearScreen(void)
{
	uint8_t i, n;
	for (i = 0; i < 8; i++)
	{
		OLED_WriteCmd(0xb0 + i); // 设置页地址（0~7）
		OLED_WriteCmd(0x00);	 // 设置显示位置—列低地址
		OLED_WriteCmd(0x10);	 // 设置显示位置—列高地址
		for (n = 0; n < 128; n++)
			OLED_WriteData(0);
	} // 更新显示
}

/**
 * @简  述  OLED指定位置显示一个ASCII字符（6X8）
 * @参  数  x：横坐标 0~123，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          ch：显示字符
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispChar(uint8_t x, uint8_t y, uint8_t ch, uint8_t mode)
{
	uint8_t c = 0, i = 0;

	c = ch - ' '; // 得到偏移后的值

	// 显示范围控制，超出部分不显示
	if ((x < 123) && (y < 8))
	{
		if (mode != 0)
		{
			OLED_Set_Pos(x, y);
			for (i = 0; i < 6; i++)
				OLED_WriteData(~ASCII6X8[c * 6 + i]);
		}
		else
		{
			OLED_Set_Pos(x, y);
			for (i = 0; i < 6; i++)
				OLED_WriteData(ASCII6X8[c * 6 + i]);
		}
	}
}

/**
 * @简  述  OLED指定位置显示ASCII字符串（8X16）,自动换行。
 * @参  数  x：横坐标 0~123，超出范围不显示
 *          y：纵坐标 0~7，超出范围不显示
 *          ch：字符串指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispStr(uint8_t x, uint8_t y, uint8_t *ch, uint8_t mode)
{
	uint8_t j = 0;

	if ((x < 123) && (y < 8))
	{
		while (ch[j] != '\0')
		{
			AX_OLED_DispChar(x, y, ch[j], mode);
			x += 6;

			if (x > 122)
			{
				x = 0;
				y += 1; // 当y>6时，超出部分不再显示
			}
			j++;
		}
	}
}
/**
 * @简  述  OLED指定位置显示一个数字（8X16）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          num：显示的数字
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispNum(uint8_t x, uint8_t y, uint8_t num, uint8_t mode) // 显示单个数字
{
	if (num < 10)
	{
		AX_OLED_DispChar(x, y, ('0' + num), mode);
	}
}

/**
 * @简  述  OLED指定位置显示数值（可显示负数）
 * @参  数  x：横坐标 0~122，超出范围不显示
 *          y：纵坐标 0~7，超出范围不显示
 *          value：显示的数值，32位整型数据，最大显示10位（inte + deci < 10）
 *          inte：整数位数，范围（>0, inte + deci < 10）
 *          deci：小数位数, 范围（inte + deci < 10）（取0时，为整数）
 *          mode：显示模式，0-正常显示，1-反白显示
 * @说  明  输入value为整型，如果是小数，可扩大相应倍数实现。例如123.45,先乘100，显示时整数位取3，小数位取2。
 *          如果显示数据可能为负数时，整数位数需要比最大位数多一位。例如-122，整数位取4。
 * @返回值
 */
void AX_OLED_DispValue(uint8_t x, uint8_t y, int32_t value, uint8_t inte, uint8_t deci, uint8_t mode)
{

	uint8_t dis_number[10];
	uint8_t i;
	uint32_t temp;

	if ((inte > 0) && ((inte + deci) < 10))
	{
		if (value < 0)
			temp = (0 - value);
		else
			temp = value;

		dis_number[1] = temp % 10;
		dis_number[2] = (temp % 100) / 10;
		dis_number[3] = (temp % 1000) / 100;
		dis_number[4] = (temp % 10000) / 1000;
		dis_number[5] = (temp % 100000) / 10000;
		dis_number[6] = (temp % 1000000) / 100000;
		dis_number[7] = (temp % 10000000) / 1000000;
		dis_number[8] = (temp % 100000000) / 10000000;
		dis_number[9] = (temp % 1000000000) / 100000000;

		if (value < 0)
		{
			AX_OLED_DispChar(x, y, '-', 0);

			for (i = 0; i < (inte - 1); i++)
			{
				AX_OLED_DispNum(x + 6 + (i * 6), y, dis_number[inte + deci - i - 1], mode);
			}
			if (deci != 0)
			{
				AX_OLED_DispChar(x + (inte * 6), y, '.', mode);

				for (i = 0; i < deci; i++)
				{
					AX_OLED_DispNum(x + (inte * 6) + 6 + (i * 6), y, dis_number[deci - i], mode);
				}
			}
		}
		else
		{
			for (i = 0; i < inte; i++)
			{
				AX_OLED_DispNum(x + (i * 6), y, dis_number[inte + deci - i], mode);
			}
			if (deci != 0)
			{
				AX_OLED_DispChar(x + (inte * 6), y, '.', mode);

				for (i = 0; i < deci; i++)
				{
					AX_OLED_DispNum(x + (inte * 6) + 6 + (i * 6), y, dis_number[deci - i], mode);
				}
			}
		}
	}
	else // 参数错误
	{
		// AX_OLED_DisplayChar(x, y, 'X', 0);
	}
}

/**
 * @简  述  OLED指定位置显示一个ASCII字符（8X16）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          ch：显示字符
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_Disp16Char(uint8_t x, uint8_t y, uint8_t ch, uint8_t mode)
{
	uint8_t c = 0, i = 0;

	c = ch - ' '; // 得到偏移后的值

	// 显示范围控制，超出部分不显示
	if ((x < 121) && (y < 7))
	{
		if (mode != 0)
		{
			OLED_Set_Pos(x, y);
			for (i = 0; i < 8; i++)
				OLED_WriteData(~ASCII8X16[c * 16 + i]);
			OLED_Set_Pos(x, y + 1);
			for (i = 0; i < 8; i++)
				OLED_WriteData(~ASCII8X16[c * 16 + i + 8]);
		}
		else
		{
			OLED_Set_Pos(x, y);
			for (i = 0; i < 8; i++)
				OLED_WriteData(ASCII8X16[c * 16 + i]);
			OLED_Set_Pos(x, y + 1);
			for (i = 0; i < 8; i++)
				OLED_WriteData(ASCII8X16[c * 16 + i + 8]);
		}
	}
}

/**
 * @简  述  OLED指定位置显示ASCII字符串（8X16）,自动换行。
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          ch：字符串指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_Disp16Str(uint8_t x, uint8_t y, uint8_t *ch, uint8_t mode)
{
	uint8_t j = 0;

	if ((x < 121) && (y < 7))
	{
		while (ch[j] != '\0')
		{
			AX_OLED_Disp16Char(x, y, ch[j], mode);
			x += 8;

			if (x > 120)
			{
				x = 0;
				y += 2; // 当y>6时，超出部分不再显示
			}
			j++;
		}
	}
}

/**
 * @简  述  OLED指定位置显示一个数字（8X16）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          num：显示的数字
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_Disp16Num(uint8_t x, uint8_t y, uint8_t num, uint8_t mode) // 显示单个数字
{
	if (num < 10)
	{
		AX_OLED_Disp16Char(x, y, ('0' + num), mode);
	}
}

/**
 * @简  述  OLED指定位置显示数值（可显示负数）
 * @参  数  x：横坐标 0~120，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          value：显示的数值，32位整型数据，最大显示10位（inte + deci < 10）
 *          inte：整数位数，范围（>0, inte + deci < 10）
 *          deci：小数位数, 范围（inte + deci < 10）（取0时，为整数）
 *          mode：显示模式，0-正常显示，1-反白显示
 * @说  明  输入value为整型，如果是小数，可扩大相应倍数实现。例如123.45,先乘100，显示时整数位取3，小数位取2。
 *          如果显示数据可能为负数时，整数位数需要比最大位数多一位。例如-122，整数位取4。
 * @返回值
 */
void AX_OLED_Disp16Value(uint8_t x, uint8_t y, int32_t value, uint8_t inte, uint8_t deci, uint8_t mode)
{

	uint8_t dis_number[10];
	uint8_t i;
	uint32_t temp;

	if ((inte > 0) && ((inte + deci) < 10))
	{
		if (value < 0)
			temp = (0 - value);
		else
			temp = value;

		dis_number[1] = temp % 10;
		dis_number[2] = (temp % 100) / 10;
		dis_number[3] = (temp % 1000) / 100;
		dis_number[4] = (temp % 10000) / 1000;
		dis_number[5] = (temp % 100000) / 10000;
		dis_number[6] = (temp % 1000000) / 100000;
		dis_number[7] = (temp % 10000000) / 1000000;
		dis_number[8] = (temp % 100000000) / 10000000;
		dis_number[9] = (temp % 1000000000) / 100000000;

		if (value < 0)
		{
			AX_OLED_Disp16Char(x, y, '-', 0);

			for (i = 0; i < (inte - 1); i++)
			{
				AX_OLED_Disp16Num(x + 8 + (i * 8), y, dis_number[inte + deci - i - 1], 0);
			}
			if (deci != 0)
			{
				AX_OLED_Disp16Char(x + (inte * 8), y, '.', 0);

				for (i = 0; i < deci; i++)
				{
					AX_OLED_Disp16Num(x + (inte * 8) + 8 + (i * 8), y, dis_number[deci - i], 0);
				}
			}
		}
		else
		{
			for (i = 0; i < inte; i++)
			{
				AX_OLED_Disp16Num(x + (i * 8), y, dis_number[inte + deci - i], 0);
			}
			if (deci != 0)
			{
				AX_OLED_Disp16Char(x + (inte * 8), y, '.', 0);

				for (i = 0; i < deci; i++)
				{
					AX_OLED_Disp16Num(x + (inte * 8) + 8 + (i * 8), y, dis_number[deci - i], 0);
				}
			}
		}
	}
	else // 参数错误
	{
		// AX_OLED_DisplayChar(x, y, 'X', 0);
	}
}

/**
 * @简  述  OLED指定位置显示一个汉字（16X16）
 * @参  数  x：横坐标 0~112，超出范围不显示
 *          y：纵坐标 0~6，超出范围不显示
 *          pbuf：汉字编码数据指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispChinese(uint8_t x, uint8_t y, const uint8_t *pbuf, uint8_t mode)
{
	uint8_t t;

	if ((x < 113) && (y < 7))
	{
		if (mode)
		{
			OLED_Set_Pos(x, y);
			for (t = 0; t < 16; t++)
			{
				OLED_WriteData(~*pbuf++);
			}
			OLED_Set_Pos(x, y + 1);
			for (t = 0; t < 16; t++)
			{
				OLED_WriteData(~*pbuf++);
			}
		}
		else
		{
			OLED_Set_Pos(x, y);
			for (t = 0; t < 16; t++)
			{
				OLED_WriteData(*pbuf++);
			}
			OLED_Set_Pos(x, y + 1);
			for (t = 0; t < 16; t++)
			{
				OLED_WriteData(*pbuf++);
			}
		}
	}
}

/**
 * @简  述  OLED指定位置显示一个指定尺寸照片
 * @参  数  x：图片起始点横坐标，0~127
 *          y：图片起始点纵坐标，0~7
 *          xsize：X轴图片尺寸，1~128
 *          ysize：Y轴图片尺寸，1~8
 *          pbuf：照片数据指针
 *          mode：显示模式，0-正常显示，1-反白显示
 * @返回值
 */
void AX_OLED_DispPicture(uint8_t x, uint8_t y, uint8_t xsize, uint8_t ysize, const uint8_t *pbuf, uint8_t mode)
{
	uint8_t tx, ty;

	// 参数过滤
	if (x > 127 || y > 7)
		return;
	if ((x + xsize) > 128 || (y + ysize) > 8)
		return;

	if (mode)
	{
		for (ty = y; ty < ysize; ty++)
		{
			OLED_Set_Pos(x, ty);
			for (tx = x; tx < xsize; tx++)
			{
				OLED_WriteData(~*pbuf++);
			}
		}
	}
	else
	{
		for (ty = y; ty < ysize; ty++)
		{
			OLED_Set_Pos(x, ty);
			for (tx = x; tx < xsize; tx++)
			{
				OLED_WriteData(*pbuf++);
			}
		}
	}
}

/* 底层操作函数-----------------------------------------------------*/
/**
 * @简  述  SPI3写入读取一个字节函数
 * @参  数  dat：要写入的字节
 * @返回值  读出字节
 */
/* 底层操作函数-----------------------------------------------------*/
/**
 * @简  述  SPI1写入读取一个字节函数
 * @参  数  dat：要写入的字节
 * @返回值  读出字节
 */
static uint8_t SPI1_Send_Byte(uint8_t dat)
{
    uint8_t rx_data;
    
    /* 使用HAL_SPI_TransmitReceive进行全双工SPI通信 */
    HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(&hspi1, &dat, &rx_data, 1, HAL_MAX_DELAY);
    
    /* 可选：检查传输状态 */
    if (status != HAL_OK) {
        // 处理错误，这里可以根据需要添加错误处理代码
        // 例如：Error_Handler();
    }
    
    /* 返回接收到的数据 */
    return rx_data;
}

/**
 * @brief
 * @param  None
 * @retval None
 */
static void OLED_WriteCmd(uint8_t data)
{
	OLED_DC_L();

	SPI1_Send_Byte(data); // 发送片擦除命令

	OLED_DC_H();
}

/**
 * @brief
 * @param  None
 * @retval None
 */
static void OLED_WriteData(uint8_t data)
{

	SPI1_Send_Byte(data); // 发送片擦除命令
}

static void OLED_Set_Pos(uint8_t x, uint8_t y)
{
	OLED_WriteCmd(0xb0 + y);
	OLED_WriteCmd(((x & 0xf0) >> 4) | 0x10);
	OLED_WriteCmd((x & 0x0f) | 0x01);
}

/******************* (C) 版权 2023 XTARK **************************************/
